package pkg.orange.online;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URLEncoder;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.cookie.CookieSpec;
import org.apache.http.cookie.CookieSpecFactory;
import org.apache.http.impl.client.AbstractHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;

import android.os.SystemClock;
//import android.util.Log;

/**
 * Klasa obsługująca konto Orange Online. 
 * 
 */

public class OrangeHttpClient {
	
	/**
	 * Klient HTTP. Przechowuje ciasteczka, co pozwala podtrzymywać sesję, obsługuje przekierowania...
	 */
	private HttpClient client;
	
	/**
	 * Login (lub numer telefonu) do Orange Online
	 */
	private String username; 
	
	/**
	 * Hasło do Orange Online
	 */
	private String password;
	
	/**
	 * Odpowiedź na żądanie logowania. Pozwala odczytać stronę uzyskaną po zalogowaniu
	 */
	private HttpResponse loginResponse; 
	
	/**
	 * Lista danych, które pobieramy
	 */
    private List<OrangeData> orangeDataList; 
    
    /**
     * Pozwala określić, ile maksymalnie wykonywać prób przy pobieraniu tabelki z danymi
     */
    private int maxTryCount; 
    
    /**
     * Określa czas oczekiwania pomiędzy kolejnymi próbami przy pobieraniu tabelki z danymi (ms)
     */
    private int sleepTime; 
    
	/**
	 * Konstruktor, przygotowuje klienta http i przygotowuje pozostałe dane
	 * @param username - login do Orange Online
	 * @param password - hasło do Orange Online
	 */
	public OrangeHttpClient(String username, String password){
		client = getNewHttpClient();
		this.username = username;
		this.password = password;
		orangeDataList = new ArrayList<OrangeData>(6);
		maxTryCount = 6;
		sleepTime = 15000;
	}
	
	/**
	 * Dodaje nazwę danych, które będą wyszukiwane na stronie
	 * @param formName Pole wyświetlane na stronie orange lub jego fragment. 
	 * 					Przykładowo dla "Środki do wykorzystania do innych sieci w ramach Gotowy do Rozmowy"
	 * 					wystarczy "środki do wykorzystania do innych sieci" (o ile jest to unikalne na całej stronie)
	 * @param displayName Nazwa wyświetlana użytkownikowi, np. "Pozostałe środki"
	 */
	public void addData(String formName, String displayName){
		orangeDataList.add(new OrangeData(formName, displayName));
	}
	
	/**
	 * Pozwala określić, ile maksymalnie wykonywać prób przy pobieraniu tabelki z danymi
	 */
	public void setMaxTryCount(int maxTryCount){
		this.maxTryCount = maxTryCount;
	}
	
	/**
	 * Określa czas oczekiwania pomiędzy kolejnymi próbami przy pobieraniu tabelki z danymi (ms)
	 */
	public void setSleepTime(int sleepTime){
		this.sleepTime = sleepTime;
	}
	
	/**
	 * Metoda pozwalająca na zalogowanie w serwisie.
	 * @param loginAddress Adres zawierający parametr backUrl, na który zostaniemy przekierowani
	 * 			przez serwer po zalogowaniu. Np. https://www.orange.pl/zaloguj.phtml?backUrl=/portal/ecare/packages
	 * @throws ClientProtocolException 
	 * @throws IOException
	 * @throws OrangeLoginException
	 */
	public void login(String loginAddress) throws ClientProtocolException, IOException, OrangeLoginException{
		//Log.d("OrangeHttpClient", "login: poczatek");
		//żądanie
		HttpGet request = prepareGetRequest(loginAddress);
		//wykonanie żądania i uzyskanie odpowiedzi
		HttpResponse response = client.execute(request);
		//pobranie klucza sesji
		String sessionConf = getSessionConf(response);

		//nowe zapytanie POST
		HttpPost loginRequest = preparePostRequest("https://www.orange.pl/zaloguj.phtml?_DARGS=/ocp/gear/infoportal/portlets/login/login-box.jsp");
		loginRequest.setHeader("Referer", loginAddress);
		
		System.out.println(username);
		System.out.println(URLEncoder.encode(password,"ISO-8859-1"));
		//przygotowanie formularza
		List <NameValuePair> nvps = new ArrayList <NameValuePair>(17);
		nvps.add(new BasicNameValuePair("_dyncharset", "UTF-8"));
		nvps.add(new BasicNameValuePair("_dynSessConf", sessionConf));
		nvps.add(new BasicNameValuePair("/tp/core/profile/login/ProfileLoginFormHandler.loginErrorURL", "https://www.orange.pl/zaloguj.phtml"));
		nvps.add(new BasicNameValuePair("_D:/tp/core/profile/login/ProfileLoginFormHandler.loginErrorURL", " "));
		nvps.add(new BasicNameValuePair("/tp/core/profile/login/ProfileLoginFormHandler.loginSuccessURL", ""));
		nvps.add(new BasicNameValuePair("_D:/tp/core/profile/login/ProfileLoginFormHandler.loginSuccessURL", " "));
		nvps.add(new BasicNameValuePair("/tp/core/profile/login/ProfileLoginFormHandler.firstEnter", "true"));
		nvps.add(new BasicNameValuePair("_D:/tp/core/profile/login/ProfileLoginFormHandler.firstEnter", " "));
		nvps.add(new BasicNameValuePair("/tp/core/profile/login/ProfileLoginFormHandler.value.login", username));
		nvps.add(new BasicNameValuePair("_D:/tp/core/profile/login/ProfileLoginFormHandler.value.login", " "));
		nvps.add(new BasicNameValuePair("/tp/core/profile/login/ProfileLoginFormHandler.value.password", password));
		nvps.add(new BasicNameValuePair("_D:/tp/core/profile/login/ProfileLoginFormHandler.value.password", " "));
		nvps.add(new BasicNameValuePair("_D:/tp/core/profile/login/ProfileLoginFormHandler.rememberMe", " "));
		nvps.add(new BasicNameValuePair("/tp/core/profile/login/ProfileLoginFormHandler.login.x", "0"));
		nvps.add(new BasicNameValuePair("/tp/core/profile/login/ProfileLoginFormHandler.login.y", "0"));
		nvps.add(new BasicNameValuePair("_D:/tp/core/profile/login/ProfileLoginFormHandler.login", " "));
		nvps.add(new BasicNameValuePair("_DARGS", "/ocp/gear/infoportal/portlets/login/login-box.jsp"));

		loginRequest.setEntity(new UrlEncodedFormEntity(nvps,"UTF-8"));
		
		HttpContext context = new BasicHttpContext(); 
		loginResponse = client.execute(loginRequest, context);
		HttpUriRequest uriRequest = (HttpUriRequest) context.getAttribute(ExecutionContext.HTTP_REQUEST); 
		//Log.d("OrangeHttpClient", uriRequest.getURI().toString());
		//Odczytujemy adres strony, na który zostaliśmy przekierowani
		if(uriRequest.getURI().toString().contains("zaloguj"))
			throw new OrangeLoginException();
		//Log.d("OrangeHttpClient", "login: koniec");
	}
	
	/** Metoda pozwalająca na wylogowanie z serwisu
	 * @throws ClientProtocolException
	 * @throws IOException
	 */
	public void logout() throws ClientProtocolException, IOException{
		//Log.d("OrangeHttpClient", "logout: poczatek");
		HttpPost request = preparePostRequest("https://www.orange.pl/portal/ecare/packages?_DARGS=/portal/layoutTemplates/html/navi_infprtl_map_lvl1.jsp.logoutForm");
		List <NameValuePair> nvps = new ArrayList <NameValuePair>(8);
		nvps.add(new BasicNameValuePair("_dyncharset", "UTF-8"));
		nvps.add(new BasicNameValuePair("/amg/ptk/map/core/formhandlers/AdvancedProfileFormHandler.logoutSuccessURL", "/portal/ecare/packages"));
		nvps.add(new BasicNameValuePair("_D:/amg/ptk/map/core/formhandlers/AdvancedProfileFormHandler.logoutSuccessURL", " "));
		nvps.add(new BasicNameValuePair("/amg/ptk/map/core/formhandlers/AdvancedProfileFormHandler.logoutErrorURL", "/zaloguj.phtml"));
		nvps.add(new BasicNameValuePair("_D:/amg/ptk/map/core/formhandlers/AdvancedProfileFormHandler.logoutErrorURL", " "));
		nvps.add(new BasicNameValuePair("/amg/ptk/map/core/formhandlers/AdvancedProfileFormHandler.logout", "wyloguj się"));
		nvps.add(new BasicNameValuePair("_D:/amg/ptk/map/core/formhandlers/AdvancedProfileFormHandler.logout", " "));
		nvps.add(new BasicNameValuePair("_DARGS", "/ocp/gear/infoportal/header/user_profile.jsp.logoutForm"));
		request.setEntity(new UrlEncodedFormEntity(nvps,"UTF-8"));
		@SuppressWarnings("unused")
		HttpResponse response = client.execute(request);
		//Log.d("OrangeHttpClient", "logout: koniec");
		
	}
	
	/**
	 * Pobiera dane z Orange Online. Wykorzystuje listę zawierającą obiekty OrangeData.
	 * Na podstawie podanych nazw, wypełnia wartość
	 * @return List<OrangeData>
	 * @throws ClientProtocolException
	 * @throws IOException
	 */
	public List<OrangeData> fetchData() throws ClientProtocolException, IOException{
		//Log.d("OrangeHttpClient", "fetchData: poczatek");
		//Wykorzystywana jest odpowiedź na żądanie, która została wygenerowana przy logowaniu
		List<String> ajaxAddrTable = getAjaxAddrTable(loginResponse);
		
		//trzeba teraz uzyskac dane, ktore sa pod dwoma adresami z AjaxAddrTable
		for (String ajaxAddr : ajaxAddrTable){
			parseAjaxTable(ajaxAddr);
		}
		//Log.d("OrangeHttpClient", "fetchData: koniec");
		return orangeDataList;	
	}
	
	
	
	/**
	 * Przygotowuje klienta HTTP, który nie weryfikuje certyfikatów
	 * Ze względu na problemy z certyfikatami u Orange
	 * @return skonfigurowany klient HTTP
	 */
	private HttpClient getNewHttpClient() {
		HttpClient client;
	    try {
	        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
	        trustStore.load(null, null);

	        SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
	        sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

	        HttpParams params = new BasicHttpParams();
	        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);

	        SchemeRegistry registry = new SchemeRegistry();
	        registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
	        registry.register(new Scheme("https", sf, 443));

	        ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);

	        client = new DefaultHttpClient(ccm, params);
	    } catch (Exception e) {
	        client = new DefaultHttpClient();
	    }
	    ((AbstractHttpClient) client).getCookieSpecs().register("myCookieSpec", new CookieSpecFactory() {
	        public CookieSpec newInstance(HttpParams params) {
	            return new MyCookieSpec();
	        }
	    });
	HttpClientParams.setCookiePolicy(client.getParams(), "myCookieSpec");
	
	return client;
	}
	
	/**
	 * Przygotowuje żądanie HTTP GET z nagłówkami typowymi dla przegląðarki internetowej
	 */
	private HttpGet prepareGetRequest(String address){
		HttpGet request = new HttpGet(address);
		request.setHeader("User-Agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:12.0) Gecko/20100101 Firefox/12.0");
		request.setHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
		request.setHeader("Accept-Language", "pl,en-us;q=0.7,en;q=0.3");
		request.setHeader("Accept-Encoding", "deflate");
		request.setHeader("Connection", "keep-alive");
		return request;
	}
	
	/**
	 * Przygotowuje żądanie HTTP GET z nagłówkami typowymi dla przeglądarki (dane Ajax)
	 */
	private HttpGet prepareAjaxGetRequest(String address){
		HttpGet request = new HttpGet(address);
		request.setHeader("User-Agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:12.0) Gecko/20100101 Firefox/12.0");
		request.setHeader("Accept", "Accept: text/javascript, text/html, application/xml, text/xml, */*");
		request.setHeader("Accept-Language", "pl,en-us;q=0.7,en;q=0.3");
		request.setHeader("Accept-Encoding", "deflate");
		request.setHeader("Connection", "keep-alive");
		request.setHeader("X-Requested-With", "XMLHttpRequest");
		request.setHeader("X-Prototype-Version", "1.7");
		request.setHeader("Cache-Control", "no-cache");
		request.setHeader("Pragma", "no-cache");
		return request;
	}
	
	/**
	 * Przygotowuje żądanie HTTP POST z nagłówkami typowymi dla przeglądarki 
	 */
	private HttpPost preparePostRequest(String address){
		HttpPost request = new HttpPost(address);
		request.setHeader("User-Agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:12.0) Gecko/20100101 Firefox/12.0");
		request.setHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
		request.setHeader("Accept-Language", "pl,en-us;q=0.7,en;q=0.3");
		request.setHeader("Accept-Encoding", "deflate");
		request.setHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
		request.setHeader("Connection", "keep-alive");
		return request;
	}
	
	/**
	 * 
	 * @param response - odpowiedź na żądanie, wykorzystywane do parsowania strony
	 * @return klucz sesji, który jest wykorzystywany jako ukryte pole w formularzu logowania
	 * @throws IllegalStateException
	 * @throws IOException
	 */
	private String getSessionConf(HttpResponse response) throws IllegalStateException, IOException{
		//treść strony do bufora
		BufferedReader br = new BufferedReader(new InputStreamReader(response.getEntity().getContent()), 8192);
		String line;
		int tmppos, tmppos2, posbegin, posend;
		String sessionConf="";
		//Szukamy klucza sesji, który jest ukrytym polem w formularzu logowania
		while ((line = br.readLine()) != null){ 
			tmppos = line.indexOf("_dynSessConf");
			if(tmppos>-1){ //mamy linie z kodem
				tmppos2 = line.indexOf("value=\"", tmppos);
				posbegin=tmppos2+7;
				posend=line.indexOf('"', posbegin);
				sessionConf=line.substring(posbegin, posend);
				//Log.d("OrangeHttpClient", "getSessionConf: sessionConf: " + sessionConf);
			}		
		}
		br.close();
		return sessionConf;
	}
	
	/**
	 * Zwraca listę adresów, które zawierają tabelki z środkami na koncie.
	 * Normalnie przeglądarka wywołuje to za pomocą ajaxa
	 * Interesujacy nas fragment:
	 * {PTK.Ajax.generateRefreshes("{g:'packages-tab0',a:'/gear/ecare/infoservices/ajax',
	 * p:'toUpdate=tab0&tabId=0&pageId=999195700018&tabsReq=true',e:'packages-tab',n:4,d:1,t:4,s:
	 * 'tab0',ex:'error-info'},{g:'packages-tab2',a:'/gear/ecare/infoservices/ajax',p:
	 * 'toUpdate=tab2&tabId=2&pageId=999195700018&tabsReq=true',e:'packages-tab',
	 * n:4,d:1,t:4,s:'tab2',ex:'error-info'},");
	 * Ja mam 2 tabelki, ale nie wiem czy wszyscy tak mają.
	 * Dlatego rozwiązanie jest dosyć uniwersalne i zwraca listę
	 * @param response - odpowiedź na żądanie, wykorzystywane do parsowania strony
	 * @return List<String> - adresy http z tabelkami zawierającymi dane
	 */
	private List<String> getAjaxAddrTable(HttpResponse response) throws IllegalStateException, IOException{
		BufferedReader br = new BufferedReader(new InputStreamReader(response.getEntity().getContent()), 8192);
		
    	int tmppos, groupposbegin, groupposend, posbegin, posend;
    	String tmpStr;
    	String AjaxAddr;
		String line;
   
    	List<String> ajaxAddrTable = new ArrayList<String>();
		while ((line = br.readLine()) != null){
			tmppos = line.indexOf("PTK.Ajax.generateRefreshes");
			if(tmppos>-1){ 
				groupposend=0;
				int endIndex = line.indexOf(")",tmppos);
				while((groupposbegin=line.indexOf('{', groupposend))<endIndex){
					if(groupposbegin==-1)
						break;
					AjaxAddr = "https://www.orange.pl";
					groupposend=line.indexOf('}', groupposbegin);
					tmpStr = line.substring(groupposbegin, groupposend);
					/*
					 * Parsowanie:
					 * g:'packages-tab0',a:'/gear/ecare/infoservices/ajax',p:'toUpdate=tab0&tabId=0&
					 * pageId=999195700018&tabsReq=true',e:'packages-tab',n:4,d:1,t:4,s:'tab0',ex:'error-info'
					 */
					posbegin=tmpStr.indexOf("a:'")+3;
					posend=tmpStr.indexOf("'", posbegin);
					AjaxAddr = AjaxAddr + tmpStr.substring(posbegin, posend);
					posbegin=tmpStr.indexOf("g:'")+3;
					posend=tmpStr.indexOf("'", posbegin);
					AjaxAddr = AjaxAddr + "?group=" + tmpStr.substring(posbegin, posend);
					posbegin=tmpStr.indexOf("e:'")+3;
					posend=tmpStr.indexOf("'", posbegin);
					AjaxAddr = AjaxAddr + "&toGet=" + tmpStr.substring(posbegin, posend);
					posbegin=tmpStr.indexOf("p:'")+3;
					posend=tmpStr.indexOf("'", posbegin);
					AjaxAddr = AjaxAddr + "&" + tmpStr.substring(posbegin, posend);	
					/*
					 * Adres wynikowy: https://www.orange.pl/gear/ecare/infoservices/ajax?group=packages-tab0&
					 * toGet=packages-tab&toUpdate=tab0&tabId=0&pageId=999195700018&tabsReq=true
					 */
					ajaxAddrTable.add(AjaxAddr);
					//Log.d("OrangeHttpClient", "getAjaxAddrTable: AjaxAddr: " + AjaxAddr);
				}
			}		
		}
		br.close();
		return ajaxAddrTable;
	}
	
	/**
	 * Parsuje Pobiera dane i parsuje (kilka prób) i uzupełnia listę 
	 * @param ajaxAddr Adres URL tabelki
	 * @throws ClientProtocolException
	 * @throws IOException
	 */
	private void parseAjaxTable(String ajaxAddr) throws ClientProtocolException, IOException{
		int tryCount=0;
		boolean tryAgain;
		do{
			tryAgain = false;
			HttpGet ajaxRequest = prepareAjaxGetRequest(ajaxAddr);
			
			HttpResponse ajaxResponse = client.execute(ajaxRequest);
			BufferedReader br = new BufferedReader(new InputStreamReader(ajaxResponse.getEntity().getContent()), 8192);
			
			int trPosBegin, trPosEnd, tdPosBegin, tdPosEnd;
	    	String cell1, cell3, cell4, line, tmpStr;
	    	//Cała tabelka jest w 1 linii, ale przeglądamy całość
			while ((line = br.readLine()) != null){ 
				if(line.contains("Twoje dane dane są właśnie wczytywane")){
					//Log.d("OrangeHttpClient","fetchData: Twoje dane dane są właśnie wczytywane, próba " + Integer.toString(tryCount));
					tryAgain = true;
					tryCount++;
					SystemClock.sleep(sleepTime);
					break;
				}
				//Mamy do przeparsowania tabelke HTML
				trPosEnd = 0;
				//Pętla po wierszach tabeli - tr
				while((trPosBegin=line.indexOf("<tr", trPosEnd))>-1){ 
					trPosEnd = line.indexOf("</tr>", trPosBegin);
					tdPosEnd = trPosBegin;
					cell1="";
					cell3="";
					cell4="";
					 //Pętla po kolumnach - td
					while((tdPosBegin=line.indexOf("<td", tdPosEnd))>-1 && tdPosBegin<trPosEnd){
						tdPosEnd = line.indexOf("</td>", tdPosBegin);
						tmpStr = line.substring(tdPosBegin, tdPosEnd);
						//Szukamy interesujacych nas wartosci
						if(tmpStr.contains("cell1")) 
							cell1 = tmpStr.substring(tmpStr.indexOf('>')+1).toLowerCase();
						if(tmpStr.contains("cell3")){
							cell3 = tmpStr.substring(tmpStr.indexOf('>')+1);
						}
						if(tmpStr.contains("cell4"))
							cell4 = tmpStr.substring(tmpStr.indexOf('>')+1);
					}
					if(cell1!="" && cell3!="" && cell4!=""){
						for (OrangeData element : orangeDataList){
							//Mamy nasz element (nazwa na stronie >= nasza nazwa)
							if(cell1.contains(element.getFormName())){
								element.setDate(cell3);
								element.setValue(cell4);
							}
						}
					}
				}
				
			}
			br.close();
		} while(tryAgain && tryCount<=maxTryCount);
		if(tryCount==maxTryCount)
			throw new IOException();
	}
	
	
}
